package com.wy.endpoint;

import java.security.Principal;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpSession;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.web.WebAttributes;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.wy.core.CustomizerOAuth2ParameterNames;

import dream.flying.flower.lang.StrHelper;
import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * 认证服务器端点
 *
 * @author 飞花梦影
 * @date 2024-09-19 15:01:55
 * @git {@link https://github.com/dreamFlyingFlower}
 */
@Controller
@AllArgsConstructor
public class AuthorizationServerEndpoint {

	private final RegisteredClientRepository registeredClientRepository;

	private final OAuth2AuthorizationConsentService authorizationConsentService;

	@ResponseBody
	@GetMapping("/user")
	public Map<String, Object> user(Principal principal) {
		if (!(principal instanceof JwtAuthenticationToken)) {
			return Collections.emptyMap();
		} else {
			JwtAuthenticationToken token = (JwtAuthenticationToken) principal;
			return token.getToken().getClaims();
		}
	}

	@GetMapping("/activate")
	public String activate(@RequestParam(value = "user_code", required = false) String userCode) {
		if (userCode != null) {
			return "redirect:/oauth2/device_verification?user_code=" + userCode;
		}
		return "device-activate";
	}

	@GetMapping("/activated")
	public String activated() {
		return "device-activated";
	}

	@GetMapping(value = "/", params = "success")
	public String success() {
		return "device-activated";
	}

	@GetMapping("/login")
	public String login(Model model, HttpSession session) {
		Object attribute = session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
		if (attribute instanceof AuthenticationException) {
			AuthenticationException exception = (AuthenticationException) attribute;
			model.addAttribute("error", exception.getMessage());
		}
		return "login";
	}

	@GetMapping(value = "/oauth2/consent")
	public String consent(Principal principal, Model model,
			@RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId,
			@RequestParam(OAuth2ParameterNames.SCOPE) String scope,
			@RequestParam(OAuth2ParameterNames.STATE) String state,
			@RequestParam(name = CustomizerOAuth2ParameterNames.USER_CODE, required = false) String userCode) {

		// Remove scopes that were already approved
		Set<String> scopesToApprove = new HashSet<>();
		Set<String> previouslyApprovedScopes = new HashSet<>();
		RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId);
		if (registeredClient == null) {
			throw new RuntimeException("客户端不存在");
		}
		OAuth2AuthorizationConsent currentAuthorizationConsent =
				this.authorizationConsentService.findById(registeredClient.getId(), principal.getName());
		Set<String> authorizedScopes;
		if (currentAuthorizationConsent != null) {
			authorizedScopes = currentAuthorizationConsent.getScopes();
		} else {
			authorizedScopes = Collections.emptySet();
		}
		for (String requestedScope : StringUtils.delimitedListToStringArray(scope, " ")) {
			if (OidcScopes.OPENID.equals(requestedScope)) {
				continue;
			}
			if (authorizedScopes.contains(requestedScope)) {
				previouslyApprovedScopes.add(requestedScope);
			} else {
				scopesToApprove.add(requestedScope);
			}
		}

		model.addAttribute("clientId", clientId);
		model.addAttribute("state", state);
		model.addAttribute("scopes", withDescription(scopesToApprove));
		model.addAttribute("previouslyApprovedScopes", withDescription(previouslyApprovedScopes));
		model.addAttribute("principalName", principal.getName());
		model.addAttribute("userCode", userCode);
		if (StrHelper.isNotBlank(userCode)) {
			model.addAttribute("requestURI", "/oauth2/device_verification");
		} else {
			model.addAttribute("requestURI", "/oauth2/authorize");
		}

		return "consent";
	}

	private static Set<ScopeWithDescription> withDescription(Set<String> scopes) {
		Set<ScopeWithDescription> scopeWithDescriptions = new HashSet<>();
		for (String scope : scopes) {
			scopeWithDescriptions.add(new ScopeWithDescription(scope));

		}
		return scopeWithDescriptions;
	}

	@Data
	public static class ScopeWithDescription {

		private static final String DEFAULT_DESCRIPTION =
				"UNKNOWN SCOPE - We cannot provide information about this permission, use caution when granting this.";

		private static final Map<String, String> scopeDescriptions = new HashMap<>();

		static {
			scopeDescriptions.put(OidcScopes.PROFILE,
					"This application will be able to read your profile information.");
			scopeDescriptions.put("message.read", "This application will be able to read your message.");
			scopeDescriptions.put("message.write",
					"This application will be able to add new messages. It will also be able to edit and delete existing messages.");
			scopeDescriptions.put("other.scope", "This is another scope example of a scope description.");
		}

		public final String scope;

		public final String description;

		ScopeWithDescription(String scope) {
			this.scope = scope;
			this.description = scopeDescriptions.getOrDefault(scope, DEFAULT_DESCRIPTION);
		}
	}
}